/*
 * ServletStringAttributeRenderer.java
 *
 * Copyright 2011 Jack Leow
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package my.edu.clhs.stringtemplate.renderer;

import org.antlr.stringtemplate.AttributeRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang.StringEscapeUtils;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.net.URLEncoder;
import java.io.UnsupportedEncodingException;

/**
 * {@link AttributeRenderer} for handling formatting of Strings using encodings
 * common to Servlet applications.
 *
 * <p><i>Note that this AttributeRenderer is stateful, and cannot be shared
 * across requests. Specifically, you will get unexpected
 * "(internal)contextAbsoluteSessionIdEmbeddedUrl" formatted Strings if you use
 * an instance from a different request</i></p>
 *
 * @author Jack Leow
 * @version 1.0
 * @since 1.0 (Mar 6, 2010, 3:50:33 PM)
 */
public class ServletStringAttributeRenderer implements AttributeRenderer {
    public static final String JAVASCRIPT_ESCAPED_FORMAT = "javaScriptEscaped";
    public static final String URL_ENCODED_FORMAT = "urlEncoded";
    public static final String HTML_ESCAPED_FORMAT = "htmlEscaped";
    public static final String XML_ESCAPED_FORMAT = "xmlEscaped";
    public static final String RAW_FORMAT = "raw";
    public static final String DEFAULT_DEFAULT_FORMAT_NAME = RAW_FORMAT;
    static final String JSTL_C_URL_FORMAT =
        "(internal)contextAbsoluteSessionIdEmbeddedUrl";

    private static final Logger log =
        LoggerFactory.getLogger(ServletStringAttributeRenderer.class);

    private static final String[] SUPPORTED_FORMAT_NAMES = {
        // Values must be alphabetical
        JSTL_C_URL_FORMAT,
        HTML_ESCAPED_FORMAT,
        JAVASCRIPT_ESCAPED_FORMAT,
        RAW_FORMAT,
        URL_ENCODED_FORMAT,
        XML_ESCAPED_FORMAT
    };

    private HttpServletRequest request;
    private HttpServletResponse response;

    private static boolean isFormatSupported(String formatName) {
        return Arrays.binarySearch(SUPPORTED_FORMAT_NAMES, formatName) >= 0;
    }

    private final String defaultFormatName;

    public String getDefaultFormatName() {
        return defaultFormatName;
    }

    public ServletStringAttributeRenderer(
            HttpServletRequest request, HttpServletResponse response,
            String defaultFormatName) {
        if (request == null || response == null || defaultFormatName == null) {
            throw new NullPointerException(
                "request, response and defaultFormatName cannot be null");
        }
        if (!isFormatSupported(defaultFormatName)) {
            throw new IllegalArgumentException(
                "unsupported default format - " + defaultFormatName);
        }
        this.request = request;
        this.response = response;
        this.defaultFormatName = defaultFormatName;
    }

    public ServletStringAttributeRenderer(
            HttpServletRequest request, HttpServletResponse response) {
        this(request, response, DEFAULT_DEFAULT_FORMAT_NAME);
    }

    public String toString(Object o, String formatName) {
        log.debug("entering toString(Object,String)");
        if (!isFormatSupported(formatName)) {
            throw new IllegalArgumentException(
                "unsupported format - " + formatName);
        }
        String input = o.toString();
        String formatted;

        if (JAVASCRIPT_ESCAPED_FORMAT.equals(formatName)) {
            formatted = StringEscapeUtils.escapeJavaScript(input);
        } else if (URL_ENCODED_FORMAT.equals(formatName)) {
            // TODO consider allowing charset to be configured
            try {
                formatted = URLEncoder.encode(input, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(
                    "platform does not support UTF-8", e);
            }
        } else if (HTML_ESCAPED_FORMAT.equals(formatName)) {
        	formatted = StringEscapeUtils.escapeHtml(input);
        } else if (XML_ESCAPED_FORMAT.equals(formatName)) {
            formatted = StringEscapeUtils.escapeXml(input);
        } else if (JSTL_C_URL_FORMAT.equals(formatName)) {
            String encoded = response.encodeURL(input);
            formatted = input.startsWith("/") ?
                request.getContextPath() + encoded : encoded;
        } else {
            formatted = input;
        }

        log.debug("exiting toString(Object,String)");
        return formatted;
    }

    public String toString(Object o) {
        log.debug("entering toString(Object)");
        log.debug("exiting toString(Object)");
        return toString(o, getDefaultFormatName());
    }
}